module(...,package.seeall)
--官方库
require "http"
require "pm"

--其他
require "msgProc"



--保持系统处于唤醒状态，不会休眠
pm.wake("mcuart")

--定义变量
--是否允许发送透传的登录报文
local tranLoginSendCnt = 0
--是否允许连接到透传服务器
local allowConnTranSvr = true
--是否已经连接到透传服务器
local tranSvrConn = false
--是否可以开启透传
allowTranSvr = false
--是否允许持续发送登录报文
local continueSndLgnPkg = true

--定义client变量
local asyncClient

--是否正在下载202固件
local dwn202FW = false
--是否正在下载飞控固件
local dwnfcFw = false
--飞控固件的table
fcFWT={}

--uart 接收缓冲区
--2 for mine test
--1 for product goods
uartID = 2
uartQueue = {}
local uartBaud = 230400
local uartTimeout = 1
local lstUartRecvTime = 0
--uart send time
local uartSndTime = 0

--http请求队列 
httpReq = {}
local httpProc = false

--透传服务器
local tranSvrIP = nvm.get("tranSvrIP")
local tranSvrPort = nvm.get("tranSvrPort")
local lstNetSendTime = 0

--2g联网状态
local connStartTime = 0
local connSuccTime = 0
local lstFixTime = -1	--上次连接服务器耗时
local disConnCnt = 0





--打印运行状态相关参数
local function prtLog(trigger)
	log.info('Trigger', trigger)
	log.info("Version", rtos.get_version(), _G.PROJECT, _G.VERSION)
	log.info('sim', nvm.get("simSelectIndex"), sim.getIccid(), link.getCurAPN())
	log.info('freeRam', _G.collectgarbage("count"), "K Bytes")
	log.info('freeRom', rtos.get_fs_free_size()/1000, "K Bytes")
	log.info('vBat', misc.getVbatt())
	log.info('CSQ', net.getRssi())
	log.info('httpQueue', #httpReq)
	log.info('bootTime', nvm.get("bootTime"), nvm.get("lstBootTime"))
	log.info('netWork', sim.getStatus(), socket.isReady())
	log.info('transSvr', allowTranSvr, tranSvrConn, allowConnTranSvr, continueSndLgnPkg, tranLoginSendCnt)
	log.info('uartSta', clib.uartSta['uart1']['open'], clib.uartSta['uart1']['baud'], clib.uartSta['uart2']['open'], clib.uartSta['uart2']['baud'])
	log.info('updateSta', updFC.fcUpdateProc, updFC.fcUpdateSta, updFC.fcCurUpd)
	
	if trigger == "bootSucc" then
		log.info('[pwronReason]', rtos.poweron_reason())
		libErr = io.readFile("/lib_err.txt") or ""
		luaErr = io.readFile("/luaerrinfo.txt") or ""
		log.info('[pwronLog]', libErr, luaErr)
	end
end
--打印模块运行情况
sys.timerLoopStart(prtLog, 30000, "timer")



--发送透传登录报文
sys.taskInit(function()
	while true do
		sys.waitUntil("SEND_LOGIN_PKG")
		
		while true do
			--如果不允许持续发送登录报文(此轮登陆尝试，已经登陆成功/重试次数超限)，则重置计数器并跳出循环，以备下一次重连使用
			if continueSndLgnPkg==false then
				log.info("sndlgnpkg", "continueSndLgnPkg is false")
				tranLoginSendCnt = 0
				continueSndLgnPkg = true
				break
			end
			
			local curTime = tostring(os.time())
			local pkgContent = nvm.get("modIMEI")..string.char(0x00, 0x00, 0x00, 0x00, 0x00)..curTime..crypto.calcHash(curTime)..nvm.get("simICCID")..nvm.get("simIMSI")
			local pkgStr = "47010101C8"..string.toHex(pkgContent)
			local crc16mcrf4xx  = string.format("%04X",crypto.crc16("USER-DEFINED", string.fromHex(pkgStr.."F2"), 0x1021, 0xFFFF, 0x0000, 1, 1))
			crc16mcrf4xx = string.sub(crc16mcrf4xx, 3, 4)..string.sub(crc16mcrf4xx, 1, 2)
			
			pkgStr = string.fromHex("FE"..pkgStr..crc16mcrf4xx)
			log.info("lgn.pkgstr", nvm.get("modIMEI"), nvm.get("simICCID"), nvm.get("simIMSI"))
			log.info('lgn.pkgstr', pkgStr:toHex(), #pkgStr)
			
			--发送登录报文
			asyncClient:asyncSend(pkgStr)
			log.info("lgn.pkg", "sent")
			
			--登录超过10次失败，不再尝试重连
			tranLoginSendCnt = tranLoginSendCnt+1
			if tranLoginSendCnt>10 then 
				log.info("login.cnt", "maximum count, exit")
				continueSndLgnPkg = false
				allowConnTranSvr = false
			else
				sys.wait(10000)
			end
		end
		
		if allowConnTranSvr==false then break end
	end
end)
--for crc test: heartbeats "FE 09 4E 01 01 00 00 00 00 00 02 03 51 04 03 1C 7F"
--[[pkgStr = "094E01010000000000020351040332"
crc16s = string.format("%04X",crypto.crc16("USER-DEFINED", string.fromHex(pkgStr), 0x1021, 0xFFFF, 0x0000, 1, 1))
log.info("FE"..pkgStr..string.sub(crc16s,3,4)..string.sub(crc16s,1,2))]]



--异步连接服务器，透传用
sys.taskInit(function()
	while allowConnTranSvr do
		connStartTime = rtos.tick()
		--阻塞等待网络就绪
        while not socket.isReady() do sys.wait(1000) end
		asyncClient = socket.tcp()
		
		--阻塞连接服务器，2000重试一次，直到成功
        while not asyncClient:connect(tranSvrIP, tranSvrPort) do
			log.info("tranSvr.conn", "connecting", ip, port)
			sys.wait(2000)
		end
		--连接成功后，发送登录报文
		sys.publish("SEND_LOGIN_PKG")
		--更改服务器连接状态
		tranSvrConn = true
		
		--阻塞
        while (asyncClient:asyncSelect(120, "ping") and allowConnTranSvr) do end
		log.info("TranSvr.conn.err", allowConnTranSvr)
		tranSvrConn = false
		allowTranSvr = false
		disConnCnt = disConnCnt+1
        asyncClient:close()
    end
end)



--异步回调接收数据
sys.subscribe("SOCKET_RECV", function(id)
    if asyncClient.id == id then
        local data = asyncClient:asyncRecv()
        log.info("tranSvr.rcv", #data, data:sub(1, 30):toHex())
		
		--如果允许透传，则开启透传
		if allowTranSvr==true and tranSvrConn==true and updFC.fcUpdateProc==false then
			uart.write(uartID, data)
			log.info("transSvr.rcv", "uart sent")
			return
		else
			log.info("transSvr.rcv", allowTranSvr, tranSvrConn)
		end
		
		--todo 如果收到的数据满足限制条件，则改变变量状态
		if string.sub(data, 6, 6):toHex()=="C9" and string.sub(data, 7, 7):toHex()=="01" then
			log.info("rcv.auth", "auth true")
			allowTranSvr = true
			continueSndLgnPkg = false
			connSuccTime = rtos.tick()
			tranLoginSendCnt = 0
			log.info("transSvr.sta.change", allowTranSvr, tranSvrConn)
			
			--检查飞控升级
			sys.timerStart(function()
				if updFC.fcUpdateProc==false then sys.publish("FC_UPD_CHK") end
				end, 5000)
			
		end
    end
end)



--升级成功与否的回调函数（防止重启）
local function updateCbFunc(downloadResult, retryCnt)
	--打印升级包下载结果，true/false
	--（可选）下次重启后自动更新(防止云端部署错误，反复升级-重启-升级)。
	log.info("upd.cbFunc", "download bin status", downloadResult)
	
	if downloadResult==true then
		--调用nvm.remove()，清除保存的参数，使用ota包的config.lua配置
		nvm.remove()
		sys.timerStart(sys.restart, 3000, "restart to deploy update package")
	
	--如果失败3次，则检查飞控升级
	elseif retryCnt==3 then
		log.info("upd.cbFunc", "download update firmware failed. stop")
		
		--检查飞控升级
		--sys.publish("FC_UPD_CHK")
	else
		log.info("upd.cbFunc", "download update firmware failed. retry...")
	end
end



--处理http返回的数据
local function httpCbFunc(result, prompt, head, content)
	--true/false; 200; cookies = xxx; yyyyxxxx(html content or fileName)
	--[[if result and head then
        for k,v in pairs(head) do
            log.info("http.cbFnc",k..": "..v)
        end
    end]]
	
	--print htt response
	if httpReq[1]["downFile"]~=nil then
		log.info("http.cbFnc", result, prompt, httpReq[1]["Opera"], content, (content~=nil and io.fileSize(content) or "file download failed"))
	else
		log.info("http.cbFnc", result, prompt, httpReq[1]["Opera"], (content~=nil and string.len(content) or "nil"))
	end
	
	--todo 根据队列的opera进行对应操作
	if result and content then
	
		--如果是检查gsm升级的回调
		if httpReq[1]["Opera"]=="gsmUpdChk" then
			--解码json
			local jsonData, result, errinfo = json.decode(content)
			if result and type(jsonData)=="table" then
			
				--请求合法(根据address判断是否有升级)
				if jsonData["code"]==0 then
					updateURL = jsonData["data"]["address"]
					log.info("GSM.update.msg", string.perfectChinese(jsonData["message"]))
					log.info("GSM.update.addr", updateURL)
					
					--根据address判断是否有升级
					if updateURL~="" or #updateURL~=0 then
						--删除飞控固件，以免空间不足
						io.rmFile("/fc/kpp.firmware")
						--处理升级请求
						update.request(updateCbFunc, updateURL)

					--给飞控升级
					else
						--sys.publish("FC_UPD_CHK")
					end
					
				--请求非法
				elseif jsonData["code"]==1 then
					log.info("GSM.update.err", string.perfectChinese(jsonData["message"]))
				
				--未知消息
				else
					log.info("GSM.update.unknown", string.perfectChinese(jsonData["message"]))
				end

			else
				log.info("GSM.update.json", "json decode error, stop")
			end
		
		--检查飞控升级请求的回调
		elseif httpReq[1]["Opera"]=="fcUpdChk" then
			--解码json
			local jsonData, result, errinfo = json.decode(content)
			if result and type(jsonData)=="table" then
				
				--只下固件不升级
				if jsonData["code"]==0 then
					updateURL = jsonData["data"]["address"]
					if updateURL==nil then log.info("fc.update.err", "url is nil") return end
					log.info("fc.update.msg", string.perfectChinese(jsonData["message"]), updateURL)
					log.info("fc.update.down", "start to download fc firmware")
					
					--检查文件是否存在，md5是否一致，如果存在且一致，不需要下载
					if io.exists("/fc/kpp.firmware")==true then
						log.info("fc.update", "code=0, file exist, remove")
						--删除飞控固件，以免空间不足
						io.rmFile("/fc/kpp.firmware")
						--请求下载升级固件
					end
				
				--非法
				elseif jsonData["code"]==1 then
					log.info("fc.update.err", string.perfectChinese(jsonData["message"]))
					
				--有升级，且升级
				elseif jsonData["code"]==2 then
					fcFWT = {}
					for i=1,tonumber(jsonData["data"]["package_count"]) do
						local tmpTables = {md5=jsonData["data"]["url_list"][i]["md5"], sort=jsonData["data"]["url_list"][i]["sort"], type=jsonData["data"]["url_list"][i]["type"], url=jsonData["data"]["url_list"][i]["url"]}
						log.info('tmpTables', tmpTables['md5'], tmpTables['sort'], tmpTables['type'], tmpTables['url'])
						fcFWT[i] = tmpTables
					end
					log.info("fc.update.new", string.perfectChinese(jsonData["message"]))
					
					--进行下一步操作
					sys.publish("FC_UPD_START")
										
				--未知消息
				else
					log.info("fc.update.unknown", string.perfectChinese(jsonData["message"]))
				end
			else
				log.info("fc.update.json", "json decode error, stop")
			end
			
		--飞控升级文件下载
		elseif httpReq[1]["Opera"]=="dlFcPart" then
			log.info("fc.down.succ", content, io.fileSize(content))
			--校验md5
			if string.upper(fcFWT[1]["md5"])~=crypto.md5("/fc/kpp.firmware", "file") then
				log.info("fc.down.chksum", "file md5 illegal", string.upper(fcFWT[1]["md5"]), crypto.md5("/fc/kpp.firmware", "file"))
				io.rmFile("/fc/kpp.firmware")
				--下载失败，重新下载（请求失败的情况，在http请求队列中，不删除；下载成功但是md5不对，则重新下载）
				if result and content then sys.publish("FC_UPD_DWN") end
			else
				log.info("fc.down.chksum", "file md5 legal ^_^")
				--校验ok，准备升级
				--如果sort是1，那么肯定是第一个407或者第一个405，则先写入40x的控制内容；如果不是第一个，则直接写文件
				log.info("fcfwt", "sort", fcFWT[1]["sort"], "type", fcFWT[1]["type"])
				if fcFWT[1]["sort"]==1 then 
					sys.publish("FC_UPD_SND_40X")
					log.info("httpreq", "send FC_UPD_SND_40X")
				else
					sys.publish("FC_UPD_WRITE_FILE")
					log.info("httpreq", "send FC_UPD_WRITE_FILE")
				end
			end
			
		end
	end
	
	--如果请求成功，则从队列删除
	if result and content then
		table.remove(httpReq, 1)
		log.info("http.cbFnc", "done. Remove current req from httpReq", #httpReq)
	--如果请求失败，重试没有超限，则重试
	elseif httpReq[1]["maxTry"]-1>=0 then
		httpReq[1]["maxTry"] = httpReq[1]["maxTry"]-1
		httpReq[1]["Retry"] = 1
		log.info("http.cbFnc", "err. Retry")
	else
		table.remove(httpReq, 1)
		log.info("http.cbFnc", "err. Retry maximum, remove current req from httpReq")
	end
	
	httpProc = false
end



--检查httpReq队列是否有内容，如果有则进行处理
sys.taskInit(function()
	while not socket.isReady() do sys.waitUntil("IP_READY_IND") end
	while true do
		--如果http没有在处理数据，且队列内有内容，则进行处理
		--table.insert(httpReq, {Opera="xxx", Url=updateURL, Method="GET", downFile=nil, maxTry=2, Retry=0})

		--get html test
		--table.insert(httpReq, {Opera="dns", Url="http://119.29.29.29/d?dn=g.cn", Method="GET", downFile=nil, maxTry=3, Retry=0})
		
		--timeout test
		--table.insert(httpReq, {Opera="dns", Url="http://119.29.29.2/d?dn=g.cn", Method="GET", downFile=nil, maxTry=3, Retry=0})
		
		--get file test
		--table.insert(httpReq, {Opera="dlfile", Url="https://www.baidu.com/favicon.ico", Method="GET", downFile="/fcfw/ficon.ico", maxTry=3, Retry=0})
		
		if httpProc==false and #httpReq>0 then
			log.info("http.req.len", #httpReq)
			log.info("http.req.proc", httpReq[1]["Opera"], httpReq[1]["Url"], httpReq[1]["Method"], httpReq[1]["downFile"], httpReq[1]["maxTry"])
			
			if httpReq[1]["Retry"]==1 then
				sys.wait(10000)
			end
			
			http.request(httpReq[1]["Method"], httpReq[1]["Url"], nil, nil, nil, 30000, httpCbFunc, httpReq[1]["downFile"])
			httpProc = true
		end
		sys.wait(1000)
	end
end)



--检查：如果存在飞控升级文件，则校验md5，通过校验pass；没有通过则删除
--如果根本没有那个文件或目录，那么新建目录
local function chkfcFw()
	log.info("boot.fcfwCHK", "fcFilename", "/fc/kpp.firmware")
	
	--如果文件存在，则删除
	if io.exists("/fc/kpp.firmware")==true then
		log.info("boot.fcfwCHK", "file exists, remove it")
		io.rmFile("/fc/kpp.firmware")
	else
		log.info("boot.fcfwCHK", "file not exists")
		io.mkDir("/fc")
		log.info("boot.fcfwCHK", "make new directory done")
		log.info("network", "wait network ready...")
	end
end


--配置串口收发
uart.on(uartID, "receive", function(uid)
	lstUartRecvTime = os.time()
	local tmpUartRecvData = ""
	tmpUartRecvData = uart.read(uid, 4096)
	
	--[[for i=#tmpUartRecvData,1,-1 do
		--log.info(string.sub(tmpUartRecvData, i, i))
		if string.sub(tmpUartRecvData, i, i)==string.char(0x00) then
			tmpUartRecvData = string.sub(tmpUartRecvData, 1, i-1)
		end
	end]]
	
    table.insert(uartQueue, tmpUartRecvData)
    sys.timerStart(sys.publish, uartTimeout, "UART_RECV")
end)
uart.on(uartID, "sent", function() sys.publish("UART_SENT") end)

--串口发送数据
sys.subscribe("UART_SENT", function()
	log.info("uart.sent")
	uartSndTime = os.time()
end)

--串口收到数据的消息
sys.subscribe("UART_RECV", function()
	log.info("uart.read", "data from uart")
	log.info("uart.read.hex", table.concat(uartQueue):toHex())
	
	--如果当前是处于飞控升级状态，则优先处理
	if updFC.fcUpdateProc==true then
		local str = table.concat(uartQueue)
		log.info("uart.read", #str, str:toHex())
		
		if updFC.fcUpdateSta=="FC_SYNC" and str:toHex()=="1210" then
			sys.publish("FC_SYNC_DONE")
			log.info("upd.proc", "send msg FC_SYNC_DONE")
			
		elseif updFC.fcUpdateSta=="FC_ERASE" and str:toHex()=="1210" then
			sys.publish("FC_ERASE_DONE")
			log.info("upd.proc", "send msg FC_ERASE_DONE")

		elseif updFC.fcUpdateSta=="FC_SEND_FILE" and str:sub(1, 2):toHex()=="1210" then
			--如果飞控上报正确接收，则剩余待发-128
			updFC.tmpLeftSize = updFC.tmpLeftSize-128
			--文件指针向后移动128
			updFC.tmpFilePos = updFC.tmpFilePos+128
			--如果剩余待发的不足128字节，则重置为0
			if updFC.tmpLeftSize<128 then updFC.tmpLeftSize=0 end
			sys.publish("FC_SEND_FILE_DONE")
			log.info("upd.proc", "send msg FC_SEND_FILE_DONE")
		
		elseif updFC.fcUpdateSta=="FC_SEND_FILE" and str:sub(1, 2):toHex()=="1213" then
			sys.publish("FC_SEND_FILE_DONE")
			log.info("upd.proc", "send file err, retry...")
		end
		
		uartQueue = {}
	return end
	
	--如果网络就绪
	if socket.isReady() then
		--log.info("uart.read", "network ready")
		
		--如果允许透传
		if allowTranSvr==true and tranSvrConn==true then
			
			--如果上次发出时间大于间隔要求，或者缓存大于4096字节，则发出去
			--[[if os.time()-lstNetSendTime>=2 or #table.concat(uartQueue)>4096 then
				log.info("uart.read", "wait for 2s, or uartQueue>1000 bytes, send cache data")]]
			
			if rtos.tick()-lstNetSendTime>=20 then
				local tmpPendingSndData = table.concat(uartQueue)
				uartQueue = {}
				
				asyncClient:asyncSend(tmpPendingSndData)
				tmpPendingSndData = ""
				log.info("uart.read", "send data to svr")
				lstNetSendTime = rtos.tick()
			end

			--else
				--网络就绪，允许透传，但是没有没有达到时间要求，则继续缓存数据
				log.info("uart.read", "cacheing data")
			--end
		else
			log.info("uart.read", "transSvr.sta", allowTranSvr, tranSvrConn)
			-- 不允许透传，清空缓冲区
			uartQueue = {}
		end
	else
		log.info("uart.read", "network not ready")
		-- 网络未就绪，清空缓存
		uartQueue = {}
    end
end)
--判断是否正在升级，禁止透传
sys.taskInit(function()
	while true do
		sys.wait(1000)
		if updFC.fcUpdateProc==true then
			allowTranSvr = false
		end
	end
end)


--用于判断是否处于bl模式
sys.subscribe("NTP_SUCCEED", function()
	log.info("set.val", "lstUartRecvTime=", os.time())
	lstUartRecvTime = os.time()
end)
sys.timerLoopStart(function()
	--如果时间大于2019年12月11日（表示ntp已经同步成功）
	if lstUartRecvTime>1576040000 then
		
		--如果30秒没有任何数据上报，则处于BL状态，重新升级固件。
		if os.time()-lstUartRecvTime>30 then
			log.info("bl.chk", "long time in bl mode. check to update by network")
			sys.publish("FC_UPD_CHK")
		end
	end
end, 5000)
--定时上报信号状态
sys.timerLoopStart(function()	
	local curTime = tostring(os.time())
	if connSuccTime>=connStartTime then
		lstFixTime = (connSuccTime-connStartTime)/200
	else
		lstFixTime = -1
	end
	
	urlStr = nvm.get("apiSvr").."/2g/upload/signal?imei="..nvm.get("modIMEI").."&signal="
			..tostring(net.getRssi()).."&fix="..tostring(lstFixTime).."&connsvrsucc="
			..tostring(disConnCnt).."&ti="..curTime.."&es="..crypto.calcHash(curTime)
	log.info("staRpt", urlStr)
	
	--插入http请求队列
	--[[log.info("httpReq", #httpReq)
	if #httpReq~=0 then
		log.info("httpReq", httpReq[1]["Opera"], httpReq[1]["maxTry"], httpReq[1]["Retry"])
		table.insert(httpReq, 2, {Opera="gsmStaReport", Url=urlStr, Method="GET", downFile=nil, maxTry=1, Retry=0})
	else
		table.insert(httpReq, {Opera="gsmStaReport", Url=urlStr, Method="GET", downFile=nil, maxTry=1, Retry=0})
	end]]
	http.request("GET", urlStr, nil, nil, nil, 30000)
end, 60000)
--向飞控发送心跳包
sys.taskInit(function()
	while true do
		if os.time()-uartSndTime>=1 and updFC.fcUpdateProc~=true then
			uart.write(uartID, (("A1B6E6F6F6FEFEFEFEFEFE76FEFEE6760C"):fromHex()))
			log.info("fc.hrtpkg", "sending hrtpkd 1")
			sys.wait(200)
			uart.write(uartID, (("A1A7DEF6F6B0FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEEE18"):fromHex()))
			log.info("fc.hrtpkg", "sending hrtpkd 2")
			
			--sys.wait(200)
			--uart.write(uartID, (("a1f7eef6f69cfefefcfcfefefefefefefefefefefefefefefefefefefefefefefefe49fef6f6fe5a4f"):fromHex()))
		end
		sys.wait(200)
	end
end)



--启动，打印日志
prtLog("bootSucc")

--检查飞控固件合法性（上电后检查：如果存在校验md5，通过校验则保存；没有通过则删除）
sys.timerStart(chkfcFw, 2000)

--打开串口，设定波特率，用于透传
clib.uartSetup(uartID, uartBaud)


